package httpclient

import (
	"context"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"gitee.com/wuzheng0709/backend-gopkg/infrastructure/pkg/gin/log"
	"io"
	"net"
	"net/http"
	httpURL "net/url"
	"strings"
	"time"

	"github.com/gin-gonic/gin"

	"gitee.com/wuzheng0709/backend-gopkg/infrastructure/pkg/errors"
	"gitee.com/wuzheng0709/backend-gopkg/infrastructure/pkg/trace"
	"net/url"
)

const (
	// DefaultTTL 一次http请求最长执行1分钟
	DefaultTTL = time.Minute
)

// Get get 请求
func Get(url string, form httpURL.Values, options ...Option) (body []byte, err error) {
	return withoutBody(http.MethodGet, url, form, options...)
}

// Delete delete 请求
func Delete(url string, form httpURL.Values, options ...Option) (body []byte, err error) {
	return withoutBody(http.MethodDelete, url, form, options...)
}

func withoutBody(method, url string, form httpURL.Values, options ...Option) (body []byte, err error) {
	if url == "" {
		return nil, errors.New("url required")
	}

	if len(form) > 0 {
		if url, err = addFormValuesIntoURL(url, form); err != nil {
			return
		}
	}

	ts := time.Now()

	opt := getOption()
	defer func() {
		if opt.trace != nil {
			opt.dialog.Success = err == nil
			opt.dialog.CostSeconds = time.Since(ts).Seconds()
			opt.trace.AppendDialog(opt.dialog)
		}

		releaseOption(opt)
	}()

	for _, f := range options {
		f(opt)
	}
	opt.header["Content-Type"] = []string{"application/x-www-form-urlencoded; charset=utf-8"}
	if opt.trace != nil {
		opt.header[trace.Header] = []string{opt.trace.ID()}
	}

	ttl := opt.ttl
	if ttl <= 0 {
		ttl = DefaultTTL
	}

	ctx, cancel := context.WithTimeout(context.Background(), ttl)
	defer cancel()

	if opt.dialog != nil {
		decodedURL, _ := httpURL.QueryUnescape(url)
		opt.dialog.Request = &trace.Request{
			TTL:        ttl.String(),
			Method:     method,
			DecodedURL: decodedURL,
			Header:     opt.header,
		}
	}

	retryTimes := opt.retryTimes
	if retryTimes <= 0 {
		retryTimes = DefaultRetryTimes
	}

	retryDelay := opt.retryDelay
	if retryDelay <= 0 {
		retryDelay = DefaultRetryDelay
	}

	var httpCode int

	defer func() {
		if opt.alarmObject == nil {
			return
		}

		if opt.alarmVerify != nil && !opt.alarmVerify(body) && err == nil {
			return
		}

		info := &struct {
			TraceID string `json:"trace_id"`
			Request struct {
				Method string `json:"method"`
				URL    string `json:"url"`
			} `json:"request"`
			Response struct {
				HTTPCode int    `json:"http_code"`
				Body     string `json:"body"`
			} `json:"response"`
			Error string `json:"error"`
		}{}

		if opt.trace != nil {
			info.TraceID = opt.trace.ID()
		}
		info.Request.Method = method
		info.Request.URL = url
		info.Response.HTTPCode = httpCode
		info.Response.Body = string(body)
		info.Error = ""
		if err != nil {
			info.Error = fmt.Sprintf("%+v", err)
		}

		raw, _ := json.MarshalIndent(info, "", " ")
		onFailedAlarm(opt.alarmTitle, raw, opt.logger, opt.alarmObject)

	}()

	for k := 0; k < retryTimes; k++ {
		body, httpCode, err = doHTTP(ctx, method, url, nil, opt)
		if shouldRetry(ctx, httpCode) || (opt.retryVerify != nil && opt.retryVerify(body)) {
			time.Sleep(retryDelay)
			continue
		}

		return
	}
	return
}

// PostForm post form 请求
func PostForm(url string, form httpURL.Values, options ...Option) (body []byte, err error) {
	return withFormBody(http.MethodPost, url, form, options...)
}

// PostJSON post json 请求
func PostJSON(url string, raw json.RawMessage, options ...Option) (body []byte, err error) {
	return withJSONBody(http.MethodPost, url, raw, options...)
}

// PutForm put form 请求
func PutForm(url string, form httpURL.Values, options ...Option) (body []byte, err error) {
	return withFormBody(http.MethodPut, url, form, options...)
}

// PutJSON put json 请求
func PutJSON(url string, raw json.RawMessage, options ...Option) (body []byte, err error) {
	return withJSONBody(http.MethodPut, url, raw, options...)
}

// PatchFrom patch form 请求
func PatchFrom(url string, form httpURL.Values, options ...Option) (body []byte, err error) {
	return withFormBody(http.MethodPatch, url, form, options...)
}

// PatchJSON patch json 请求
func PatchJSON(url string, raw json.RawMessage, options ...Option) (body []byte, err error) {
	return withJSONBody(http.MethodPatch, url, raw, options...)
}

func withFormBody(method, url string, form httpURL.Values, options ...Option) (body []byte, err error) {
	if url == "" {
		return nil, errors.New("url required")
	}
	if len(form) == 0 {
		return nil, errors.New("form required")
	}

	ts := time.Now()

	opt := getOption()
	defer func() {
		if opt.trace != nil {
			opt.dialog.Success = err == nil
			opt.dialog.CostSeconds = time.Since(ts).Seconds()
			opt.trace.AppendDialog(opt.dialog)
		}

		releaseOption(opt)
	}()

	for _, f := range options {
		f(opt)
	}
	opt.header["Content-Type"] = []string{"application/x-www-form-urlencoded; charset=utf-8"}
	if opt.trace != nil {
		opt.header[trace.Header] = []string{opt.trace.ID()}
	}

	ttl := opt.ttl
	if ttl <= 0 {
		ttl = DefaultTTL
	}

	ctx, cancel := context.WithTimeout(context.Background(), ttl)
	defer cancel()

	formValue := form.Encode()
	if opt.dialog != nil {
		decodedURL, _ := httpURL.QueryUnescape(url)
		opt.dialog.Request = &trace.Request{
			TTL:        ttl.String(),
			Method:     method,
			DecodedURL: decodedURL,
			Header:     opt.header,
			Body:       formValue,
		}
	}

	retryTimes := opt.retryTimes
	if retryTimes <= 0 {
		retryTimes = DefaultRetryTimes
	}

	retryDelay := opt.retryDelay
	if retryDelay <= 0 {
		retryDelay = DefaultRetryDelay
	}

	var httpCode int

	defer func() {
		if opt.alarmObject == nil {
			return
		}

		if opt.alarmVerify != nil && !opt.alarmVerify(body) && err == nil {
			return
		}

		info := &struct {
			TraceID string `json:"trace_id"`
			Request struct {
				Method string `json:"method"`
				URL    string `json:"url"`
			} `json:"request"`
			Response struct {
				HTTPCode int    `json:"http_code"`
				Body     string `json:"body"`
			} `json:"response"`
			Error string `json:"error"`
		}{}

		if opt.trace != nil {
			info.TraceID = opt.trace.ID()
		}
		info.Request.Method = method
		info.Request.URL = url
		info.Response.HTTPCode = httpCode
		info.Response.Body = string(body)
		info.Error = ""
		if err != nil {
			info.Error = fmt.Sprintf("%+v", err)
		}

		raw, _ := json.MarshalIndent(info, "", " ")
		onFailedAlarm(opt.alarmTitle, raw, opt.logger, opt.alarmObject)

	}()

	for k := 0; k < retryTimes; k++ {
		body, httpCode, err = doHTTP(ctx, method, url, []byte(formValue), opt)
		if shouldRetry(ctx, httpCode) || (opt.retryVerify != nil && opt.retryVerify(body)) {
			time.Sleep(retryDelay)
			continue
		}

		return
	}
	return
}

func withJSONBody(method, url string, raw json.RawMessage, options ...Option) (body []byte, err error) {
	if url == "" {
		return nil, errors.New("url required")
	}
	if len(raw) == 0 {
		return nil, errors.New("raw required")
	}

	ts := time.Now()

	opt := getOption()
	defer func() {
		if opt.trace != nil {
			opt.dialog.Success = err == nil
			opt.dialog.CostSeconds = time.Since(ts).Seconds()
			opt.trace.AppendDialog(opt.dialog)
		}

		releaseOption(opt)
	}()

	for _, f := range options {
		f(opt)
	}
	opt.header["Content-Type"] = []string{"application/json; charset=utf-8"}
	if opt.trace != nil {
		opt.header[trace.Header] = []string{opt.trace.ID()}
	}

	ttl := opt.ttl
	if ttl <= 0 {
		ttl = DefaultTTL
	}

	ctx, cancel := context.WithTimeout(context.Background(), ttl)
	defer cancel()

	if opt.dialog != nil {
		decodedURL, _ := httpURL.QueryUnescape(url)
		opt.dialog.Request = &trace.Request{
			TTL:        ttl.String(),
			Method:     method,
			DecodedURL: decodedURL,
			Header:     opt.header,
			Body:       string(raw), // TODO unsafe
		}
	}

	retryTimes := opt.retryTimes
	if retryTimes <= 0 {
		retryTimes = DefaultRetryTimes
	}

	retryDelay := opt.retryDelay
	if retryDelay <= 0 {
		retryDelay = DefaultRetryDelay
	}

	var httpCode int

	defer func() {
		if opt.alarmObject == nil {
			return
		}

		if opt.alarmVerify != nil && !opt.alarmVerify(body) && err == nil {
			return
		}

		info := &struct {
			TraceID string `json:"trace_id"`
			Request struct {
				Method string `json:"method"`
				URL    string `json:"url"`
			} `json:"request"`
			Response struct {
				HTTPCode int    `json:"http_code"`
				Body     string `json:"body"`
			} `json:"response"`
			Error string `json:"error"`
		}{}

		if opt.trace != nil {
			info.TraceID = opt.trace.ID()
		}
		info.Request.Method = method
		info.Request.URL = url
		info.Response.HTTPCode = httpCode
		info.Response.Body = string(body)
		info.Error = ""
		if err != nil {
			info.Error = fmt.Sprintf("%+v", err)
		}

		raw, _ := json.MarshalIndent(info, "", " ")
		onFailedAlarm(opt.alarmTitle, raw, opt.logger, opt.alarmObject)

	}()

	for k := 0; k < retryTimes; k++ {
		body, httpCode, err = doHTTP(ctx, method, url, raw, opt)
		if shouldRetry(ctx, httpCode) || (opt.retryVerify != nil && opt.retryVerify(body)) {
			time.Sleep(retryDelay)
			continue
		}

		return
	}
	return
}

// PublicProxyLog [...]
type PublicProxyLog struct {
	Status  int         `gorm:"column:status;type:int(3);not null;comment:'http状态码'" json:"status"`     // http状态码
	Method  string      `gorm:"column:method;type:varchar(32);not null;comment:'http方法'" json:"method"` // http方法
	URL     string      `gorm:"column:url;type:varchar(255);not null;comment:'路径'" json:"url"`          // 路径
	Header  http.Header `gorm:"column:header;type:varchar(255);not null;comment:'请求头'" json:"header"`   // 请求头
	Body    string      `gorm:"column:body;type:text;default:null;comment:'请求body信息'" json:"body"`      // 请求body信息
	ResInfo string      `gorm:"column:res_info;type:text;not null;comment:'返回信息'" json:"res_info"`      // 返回信息
	ResErr  string      `gorm:"column:res_err;type:text;default:null" json:"res_err"`
	Latency int         `gorm:"column:latency;type:int(32);not null;comment:'延迟（单位：毫秒）'" json:"latency"` // 延迟（单位：毫秒）
	LogType string      `json:"log_type"`                                                                // 日志类型
}

func NewProxyLog(status, latency int, method, url string, header http.Header, resInfo, body, resErr string) PublicProxyLog {
	return PublicProxyLog{
		Status:  status,  // http状态码
		Method:  method,  // http方法
		URL:     url,     // 路径
		Header:  header,  // 请求头
		ResInfo: resInfo, // 返回信息
		Latency: latency, // 延迟（单位：毫秒）
		Body:    body,    // 请求body信息
		ResErr:  resErr,
		LogType: "internal_proxy", // 日志类型
	}
}

// 反向代理
func PublicRequire(url, method string, ReqByte []byte, header *http.Header, hc gin.HandlersChain, c *gin.Context) (resBody []byte, resCode int, err error) {
	// 使用现有中间件
	if len(hc) != 0 {
		for _, f := range hc {
			f(c)
		}
	}

	client, err := GetHttpClient(url)
	if err != nil {
		return resBody, http.StatusInternalServerError, err
	}

	req, err := http.NewRequest(method, url, strings.NewReader(string(ReqByte)))
	if err != nil {
		resCode = http.StatusInternalServerError
		log.Error("包装请求错误：", err)
		return
	}
	req.Header = *header
	res, err := client.Do(req)
	if err != nil {
		if res != nil {
			resCode = res.StatusCode
			log.Error("反向代理请求失败，err:", err)
			return
		}
		return
	}
	// 将调用结果写入到数据库中
	defer func(res *http.Response) {
		// TODO: 开启线程池后启用
		res.Body.Close()
	}(res)

	resBody, err = io.ReadAll(res.Body)
	if err != nil {
		resCode = http.StatusInternalServerError
		log.Error("读取 反向代理的 body 失败，err：", err.Error())
		return
	}
	resCode = res.StatusCode
	return
}

type SkipReqBody func(url string) bool

// 生成代理日志
func RetProxyLog(header http.Header, resCode int, timeNow time.Time, method string, url string, resBody, ReqByte []byte, err error) {
	var errLog string
	if err != nil {
		errLog = err.Error()
	}
	// TODO： 通过 http.Header 过滤 body
	// 反向代理执行前后的结果信息
	if header.Get(SKIPREQBODY) == "true" {
		ReqByte = []byte("跳过读取（body）")
	}
	npl := NewProxyLog(
		resCode,
		int(time.Since(timeNow).Milliseconds()),
		method,
		url,
		header,
		string(resBody),
		string(ReqByte),
		errLog,
	)

	// 数据写入 数据库 中
	nplByte, _ := json.Marshal(npl)
	log.Info(string(nplByte))
}

func GetHttpClient(url string) (*http.Client, error) {
	client := &http.Client{}
	u, err := httpURL.Parse(url)
	if err != nil {
		return nil, err
	}
	if u.Scheme == "https" && net.ParseIP(strings.Split(u.Host, ":")[0]) != nil {
		client.Transport = &http.Transport{
			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		}
	}
	return client, nil
}

const SKIPREQBODY = "SkipReqBody"

func SetSkipReqBody(header http.Header) http.Header {
	header.Set(SKIPREQBODY, "true")
	return header
}

// 反向代理 GET
func PublicRequireByGet(url string, header http.Header) (resBody []byte, resCode int, err error) {
	ft := time.Now()
	resBody, resCode, err = PublicRequireByGetWithoutLog(url, header, nil)
	RetProxyLog(header, resCode, ft, "GET", url, resBody, []byte{}, err)
	return resBody, resCode, err
}

// 反向代理 GET, 不产生反向代理日志，url为网关api中登记的接口才可调用
func PublicRequireByGetWithoutLog(url string, header http.Header, reqByte []byte) (resBody []byte, resCode int, err error) {
	hc := make(gin.HandlersChain, 0)
	resBody, resCode, err = PublicRequire(url, "GET", nil, &header, hc, nil)
	return resBody, resCode, err
}

// 反向代理 POST
func PublicRequireByPost(url string, header http.Header, reqByte []byte) (resBody []byte, resCode int, err error) {
	ft := time.Now()
	resBody, resCode, err = PublicRequireByPostWithoutLog(url, header, reqByte)
	RetProxyLog(header, resCode, ft, "POST", url, resBody, reqByte, err)
	return resBody, resCode, err
}

// 反向代理 POST, 不产生反向代理日志，url为网关api中登记的接口才可调用
func PublicRequireByPostWithoutLog(url string, header http.Header, reqByte []byte) (resBody []byte, resCode int, err error) {
	hc := make(gin.HandlersChain, 0)
	method := "POST"
	resBody, resCode, err = PublicRequire(url, method, reqByte, &header, hc, nil)
	return resBody, resCode, err
}

// 反向代理 PUT
func PublicRequireByPUT(url string, header http.Header, reqByte []byte) (resBody []byte, resCode int, err error) {
	hc := make(gin.HandlersChain, 0)
	ft := time.Now()
	method := "PUT"
	resBody, resCode, err = PublicRequire(url, method, reqByte, &header, hc, nil)
	RetProxyLog(header, resCode, ft, method, url, resBody, reqByte, err)
	return resBody, resCode, err
}

// 反向代理 PUT，不产生反向代理日志，url为网关api中登记的接口才可调用
func PublicRequireByPUTWithoutLog(url string, header http.Header, reqByte []byte) (resBody []byte, resCode int, err error) {
	hc := make(gin.HandlersChain, 0)
	method := "PUT"
	resBody, resCode, err = PublicRequire(url, method, reqByte, &header, hc, nil)
	return resBody, resCode, err

}

// 反向代理 POST 传入内容为表单
func PublicRequireByPostForm(url string, header http.Header, urlV url.Values) (resBody []byte, resCode int, err error) {
	ft := time.Now()
	resBody, resCode, err = PublicRequireByPostFormWithoutLog(url, header, urlV)
	RetProxyLog(header, resCode, ft, "POST", url, resBody, []byte(urlV.Encode()), err)
	return resBody, resCode, err
}

// 反向代理 POST 传入内容为表单，不产生反向代理日志，url为网关api中登记的接口才可调用
func PublicRequireByPostFormWithoutLog(url string, header http.Header, urlV url.Values) (resBody []byte, resCode int, err error) {
	hc := make(gin.HandlersChain, 0)
	reqByte := []byte(urlV.Encode())
	header.Set("Content-Type", "application/x-www-form-urlencoded")
	resBody, resCode, err = PublicRequire(url, "POST", reqByte, &header, hc, nil)
	return resBody, resCode, err
}

func BehaviorIsAccess(koalaUrl string) (isAccess bool, err error) {
	resBody, resCode, resErr := PublicRequireByGet(koalaUrl,
		http.Header{})
	if resCode != 200 {
		return false, resErr
	}

	var resMap map[string]interface{}
	if err := json.Unmarshal(resBody, &resMap); err != nil {
		log.Error("反序列化失败，err:", err.Error())
		return false, err
	}
	if resMap["Str_reason"].(string) == "Allow" {
		return true, nil
	}
	return false, errors.New("禁止触发当前行为")
}
